import logging
logging.disable(logging.CRITICAL)
import natsort,sys,os,json,re,librosa,math,requests,time,urllib,random,subprocess,shutil,psutil,threading
from PyQt5.QtCore import QThread, pyqtSignal,QTimer,pyqtSlot
from seleniumwire import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.edge.options import Options
from util import download as dl
from pydub import AudioSegment

class ProgramThread(QThread):
    info = pyqtSignal(str)
    error_signal = pyqtSignal(str)
    pause_signal = pyqtSignal()
    finished = pyqtSignal()
    updateBar = pyqtSignal(int)
    thread_running = pyqtSignal(bool)
    def __init__(self, username, password, T3, H, IS_TALK, LOW_SCORE, WRONG_ANSWER,implicitly_wait_time,Unit_or_Level,TTS,rf_text,rf_audio,FastRead=False):
        super().__init__()
        self.PI = {
            'A':0,
            'B':1,
            'C':2,
            'D':3,
        }
        self.username = username
        self.password = password
        self.T3 = T3 # 秒杀模式
        self.T1 = False # 判断Authorization获取成功没有
        self.H = H # 是否需要切换窗口,如果点击fif后有新的窗口就写真的，没有就写假
        self.WRONG_ANSWER = int(WRONG_ANSWER) # 随机错几个 1:错1个 2:错2个 建议1-3 太大了会超出索引
        self.IS_TALK = IS_TALK # 是否完成口语 确保adb连接手机
        self.LOW_SCORE = int(LOW_SCORE) # 最低分
        self.Authorization = None # Authorization
        self.levelcategory = [] # 0：口语 1：练习
        self.audio_info = [] # [音频名字，音频内容（en)，音频内容（zh), 音频时长]
        self.ShowType = None # 口语的类型 t7:回答问题 #目前遇到的，除了t7，其余方法都一样
        self.cookies = None # cookies,请求初始化时的cookies
        self.implicitly_wait_time = implicitly_wait_time
        self.Unit_or_Leave = Unit_or_Level
        self.IS_TTS = False
        self.rf_text = rf_text
        self.rf_audio =  rf_audio
        self.FastRead = FastRead
        self.resp_proxy = None

                
        #后续添加了TTS可以再开启下面的功能
        # if self.IS_TTS: # TTS开启但是没有指定TTS文本或TTS音频
        #     if self.rf_text == '':
        #         self.info.emit("未指定TTS文本，结束程序")
        #         self.error_signal.emit("未指定TTS文本")
        #         self.finished.emit()
        #     elif self.rf_audio == "":
        #         self.info.emit("未指定TTS音频，结束程序")
        #         self.error_signal.emit("未指定TTS音频")
        #         self.finished.emit()
        #     elif self.rf_audio == "" and self.rf_text == '':
        #         self.info.emit("未指定TTS文本和音频，结束程序")
        #         self.error_signal.emit("未指定TTS文本和音频")
        #         self.finished.emit()
        #     else:
        #         self.info.emit("TTS开启，开始加载TTS文本和音频")
    def continue_execution(self):
        self.paused = False
    def run(self):

        self.info.emit(f"""\n--------------INFO--------------
秒杀模式:{self.T3}
随机错0-{self.WRONG_ANSWER}个
是否切换窗口:{self.H}
最低分:{self.LOW_SCORE}
是否完成口语:{self.IS_TALK}
隐式等待时间:{self.implicitly_wait_time}
模式:{self.Unit_or_Leave}
是否修改数据:{self.FastRead}
TTS是否开启:{self.IS_TTS}
--------------------------------""")

        if self.IS_TALK and not self.FastRead:
            self.adb_path = os.getcwd()+r"\util\adb.exe"
            b,brand_result= self.is_adb_connected()
            if b:
                try:
                    self.info.emit("ADB连接成功 设备品牌：{}".format(brand_result.stdout.splitlines()[-1]))
                    try:
                        self.adjusted_volume() # 自动调整音量
                    except:
                        self.info.emit("自动调整手机音量失败，请手动调整，避免刷题过程中没有声音")
                        self.error_signal.emit("自动调整音量失败")
                except:
                    self.info.emit("ADB连接失败，请检查连接")
                    self.error_signal.emit("ADB连接失败，请检查连接")
                    self.finished.emit()

            else:
                self.info.emit("ADB连接失败，请检查连接")
                self.finished.emit()

        if not os.path.isdir(os.path.join(os.getcwd(), 'temp')):
            os.mkdir(os.path.join(os.getcwd(), 'temp'))
        if os.path.exists("./msedgedriver.exe"):
            dr_path = "./msedgedriver.exe"
            self.info.emit("检测到下载的driver,使用下载的driver")
        
        else:
            try:
                version = dl.get_edge_version()
                self.info.emit(f'检测到Edge浏览器版本号：{version}')
                self.info.emit('开始下载msedgedriver.exe(可能需要一些时间)')
                zip_path = dl.download_driver(version,self.updateBar)
                
                if zip_path:
                    # 解压到当前目录
                    dl.unzip_file(zip_path, os.getcwd())
                    # 删除压缩包
                    dl.delete_file_or_dir(zip_path)
                    # 删除解压出来的文件夹，保留exe文件
                    driver_dir = os.path.join(os.getcwd(), 'msedgedriver')
                    if os.path.isdir(driver_dir):
                        for file in os.listdir(driver_dir):
                            file_path = os.path.join(driver_dir, file)
                            if os.path.isfile(file_path):
                                shutil.move(file_path, os.getcwd())
                        dl.delete_file_or_dir(driver_dir)
                    try:
                        os.removedirs('Driver_Notes')
                    except:
                        pass
                    self.info.emit('清理完成')
            except:
                self.info.emit("下载driver失败，请手动到Https://developer.microsoft.com/zh-cn/microsoft-edge/tools/webdriver/下载")
                self.finished.emit()
                self.error_signal.emit("driver下载失败")
            dr_path = "./msedgedriver.exe"
            self.info.emit("下载成功，使用下载的driver")
        options = Options()
        if self.FastRead:
            seleniumwire_options = {
                'proxy': {
                    'http': 'http://127.0.0.1:8080',
                    'https': 'http://127.0.0.1:8080',
                    'no_proxy': 'localhost,127.0.0.1',  # 不需要代理的地址
                }
            }
            self.info.emit("正在打开修改器")
            proxy_process = subprocess.Popen(['start','.\\util\\proxy.exe'],shell=True)
            proxy_started = False
            while not proxy_started:
                for proc in psutil.process_iter():
                    if proc.name() == 'proxy.exe':
                        self.info.emit("修改器打开成功")
                        proxy_started = True
                        break
                else:
                    time.sleep(1)
                

        else:
            seleniumwire_options = {}
        prefs = {
            "profile.default_content_setting_values.media_stream_mic": 1,  # 1表示允许使用麦克风
            "profile.default_content_setting_values.sound": 2  # 2 表示静音
        }
        options.add_experimental_option("prefs", prefs)

        service = Service(executable_path=dr_path)
        self.driver = webdriver.Edge(service=service, options=options,seleniumwire_options=seleniumwire_options)
        self.driver.implicitly_wait(self.implicitly_wait_time)

        with open('./util/stealth.min.js') as f:
            js = f.read()
        
        self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": js 
        })
        self.info.emit("反检测脚本加载成功")
        time.sleep(1)
        self.driver.get('https://www.fifedu.com/iplat/html/index.html')
        if self.username and self.password:
            self.driver.find_element(By.CSS_SELECTOR, 'a[class="login"]').click()
            self.driver.find_element(By.CSS_SELECTOR,'input[name="user"]').send_keys(self.username)
            self.driver.find_element(By.CSS_SELECTOR,'input[name="pass"]').send_keys(self.password)
            self.driver.find_element(By.CSS_SELECTOR,'div[class="login_btn"]').click()
            self.driver.find_element(By.XPATH,'//*[@id="fif"]/div[1]/div/div[1]/ul/li[1]/ul/li[2]').click()
        self.paused = True
        self.pause_signal.emit()
        self.info.emit("点击继续按钮继续...")
        while self.paused:
            self.msleep(100)
        if self.H:
            new_window = WebDriverWait(self.driver, 10).until(EC.number_of_windows_to_be(2))
            self.driver.switch_to.window(self.driver.window_handles[-1])
        try: 
            if self.Unit_or_Leave == 'Unit':
                units = self.driver.find_elements(By.CSS_SELECTOR,'div[class="task_list"]')
                tu = 0
                while tu < len(units):
                    units = self.driver.find_elements(By.CSS_SELECTOR,'div[class="task_list"]')
                    self.info.emit(f"第{tu+1}个单元")
                    units[tu].click()
                    l = len(self.driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]'))
                    self.tp = 0
                    self.info.emit(f"共有{l}个任务")
                    self.driver.refresh()
                    self.Init(self.driver)
                    if len(self.levelcategory) == l:
                        self.info.emit("初始化成功")
                    while self.tp != l:
                        #pass_pracice = False
                        self.parts = self.driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]')
                        grades = self.parts[self.tp].find_element(By.CSS_SELECTOR,'div[class="state_box"]').text
                        if grades != "" and str(grades) != "尚未开启":
                            self.info.emit(f"{self.tp+1}已通过，跳过...")
                            self.info.emit(f"得分：{grades}")
                            if int (grades) < self.LOW_SCORE:
                                self.info.emit("不及格，重新开始答题...")
                            else:
                                self.tp += 1
                                continue
                        if str(grades) == "尚未开启":
                            self.info.emit("最后一次答题失败，或不及格，未开启下一题，重新开始答题...")
                            self.tp -= 1
                        #tag = self.parts[self.tp].find_element(By.CSS_SELECTOR,'uni-view[class="tag_box"]').find_elements(By.TAG_NAME,'div')
                        if self.IS_TALK and self.levelcategory[self.tp]==0: # 口语
                            self.info.emit("口语练习")
                            try:
                                self.talk(self.driver) 
                            except Exception as e:
                                self.info.emit(f"错误信息:{e}")
                                self.error_signal.emit("talk error")
                                QTimer.singleShot(1000, self.finished.emit)
                            continue
                        elif not self.IS_TALK and self.levelcategory[self.tp]==0:
                            self.info.emit("当前任务为口语，完成口语未开启，结束程序...")
                            self.finished.emit()
                        try:
                            self.practice(self.driver) # 答题
                        except Exception as e:
                            self.info.emit(f"错误信息:{e}")
                            self.error_signal.emit("practice error")
                            QTimer.singleShot(1000,self.finished.emit)
                    self.info.emit("完成一个单元")
                    self.T1 = False # 切换单元，要重置单元信息请求。
                    self.levelcategory = [] # 重置单元信息请求。
                    tu += 1
                    self.driver.back()
                    time.sleep(1)
                    
                self.driver.quit()
                self.info.emit("全部完成！")
                self.finished.emit()
            else: # level模式
                l = len(self.driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]'))
                self.tp = 0
                self.info.emit(f"共有{l}个任务")
                self.driver.refresh()
                self.Init(self.driver)
                if len(self.levelcategory) == l:
                    self.info.emit("初始化成功")
                while self.tp != l:
                    #pass_pracice = False
                    self.parts = self.driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]')
                    grades = self.parts[self.tp].find_element(By.CSS_SELECTOR,'div[class="state_box"]').text
                    if grades != "" and str(grades) != "尚未开启":
                        self.info.emit(f"{self.tp+1}已通过，跳过...")
                        self.info.emit(f"得分：{grades}")
                        if int (grades) < self.LOW_SCORE:
                            self.info.emit("不及格，重新开始答题...")
                        else:
                            self.tp += 1
                            continue
                    if str(grades) == "尚未开启":
                        self.info.emit("最后一次答题失败，或不及格，未开启下一题，重新开始答题...")
                        self.tp -= 1
                    #tag = self.parts[self.tp].find_element(By.CSS_SELECTOR,'uni-view[class="tag_box"]').find_elements(By.TAG_NAME,'div')
                    if self.IS_TALK and self.levelcategory[self.tp]==0: # 口语
                        self.info.emit("口语练习")
                        try:
                            self.talk(self.driver) 
                        except:
                            self.info.emit("口语答题失败,请重试...")
                            self.error_signal.emit("talk error")
                            QTimer.singleShot(1000,self.finished.emit)
                            self.finished.emit()
                        continue
                    elif not self.IS_TALK and self.levelcategory[self.tp]==0:
                        self.info.emit("口语练习，跳过...")
                        self.tp += 1
                        continue
                    try:
                        self.practice(self.driver) # 答题 
                    except:
                        self.info.emit("选择题答题失败,请重试...")
                        self.error_signal.emit("practice error")
                        QTimer.singleShot(1000,self.finished.emit)
                        self.finished.emit()
            self.driver.quit()
            self.info.emit("全部完成！")
            self.finished.emit()
            time.sleep(1)
            
        except Exception as e:
            try:
                self.driver.quit()
            except:
                pass
            self.error_signal.emit(f"失败:{e}")
            self.finished.emit()


    def _get_Authorization(self,driver):
        temp_info_list = []
        def interceptor(request):
            if 'Authorization' in request.headers and 'https://moral.fifedu.com/kyxl-app/stu/column/stuUnitInfo' in request.url and 'Cookie' in request.headers:
                Authorization, cookie_str = request.headers['Authorization'], request.headers['Cookie']
                print("Authorization:", Authorization)
                print("Cookie:", cookie_str)
                temp_info_list.append(Authorization)
                temp_info_list.append(cookie_str)
        
        driver.request_interceptor = interceptor
        driver.refresh()
        start_time = time.time()
        while len(temp_info_list) == 0:
            time.sleep(0.1)
            if int(time.time() - start_time) > 10:
                self.info.emit("初始化失败，请重试...")
                self.error_signal.emit("初始化失败，请重试...")
                self.finished.emit()
                break
        self.info.emit("截取信息成功")
        driver.request_interceptor = None
        Authorization, cookie_str = temp_info_list[0], temp_info_list[1]
        self.T1 = True
        # 将cookie字符串分割成单独的键值对
        cookie_pairs = cookie_str.split('; ')

        # 创建一个空字典来存储cookies
        cookies = {}

        # 遍历键值对，并将它们添加到字典中
        for pair in cookie_pairs:
            if '=' in pair:  # 确保键值对有效
                key, value = pair.split('=', 1)  # 分割键和值
                cookies[key.strip()] = value.strip()  # 去除可能的空格并添加到字典
                
        if Authorization != None and cookies != None:
            return Authorization,cookies
        else:
            pass
    def In_to_iframe(self,driver):
        driver.switch_to.frame(driver.find_element(By.XPATH,'/html/body/iframe'))
        driver.switch_to.frame(driver.find_element(By.XPATH,'//*[@id="iframeWindow"]'))
    def _get_answer(self,gid):
        A = [] # 储存答案的列表
        memory = [] # 储存所有的答案，后续从这里面随机删除，里面储存的是索引
        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Encoding": "gzip, deflate, br, zstd",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
            "Authorization": self.Authorization,
            "Connection": "keep-alive",
            "Content-Length": "40",
            "Content-Type": "application/x-www-form-urlencoded",
            "Host": "moral.fifedu.com",
            "Origin": "https://static.fifedu.com",
            "Referer": "https://static.fifedu.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
            "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "Windows",
            "source": "10003"
        }
        data = {
            "levelId":gid
        }
        with open("./cookie_getAnswer.json", 'r') as f:
            cookies = json.load(f)
        session = requests.Session()
        session.proxies = self.resp_proxy
        for cookie in cookies:
            session.cookies.set(cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'], secure=cookie['secure'])
        response = session.post('https://moral.fifedu.com/kyxl-app/column/getLevelInfo',headers=headers,data=data,verify=False)
        content = response.json()
        try:
            p = content['data']['content']['moshi']
            for i in p:
                if i['name'] == '挑战':
                    p = i['question']['qcontent']['item']
        except:
            self.info.emit("获取答案失败！")
            self.error_signal.emit("答案获取失败！")
            return False
        for q in p:
            o = q['questions']
            for i in o:
                A.append(i['right_answer'][0])
            #A.append("|")
        right_answer = "正确答案:"
        for __ in A:
            right_answer += __
        self.info.emit(right_answer)
        for i in range(len(A)-1):
            memory.append(i)
        if self.WRONG_ANSWER == 0:
            return A
        for _ in range(random.randint(1,self.WRONG_ANSWER)):
                index = random.choice(memory)
                memory.remove(index)
                wrong_answers = ['A', 'B', 'C', 'D']
                wrong_answers.remove(A[index])
                A[index] = random.choice(wrong_answers)  # 随机选择一个错误答案
        return A
    def calculate_time_difference(self,end_time_str, start_time_str):
        # 分解时间字符串
        end_hours, end_minutes, end_seconds = end_time_str.split(":")
        start_hours, start_minutes, start_seconds = start_time_str.split(":")
        
        end_seconds, end_milliseconds = end_seconds.split(".")
        start_seconds, start_milliseconds = start_seconds.split(".")
        
        # 将小时、分钟和秒转换为总秒数
        end_total_seconds = int(end_hours) * 3600 + int(end_minutes) * 60 + int(end_seconds) + int(end_milliseconds) / 1000.0
        start_total_seconds = int(start_hours) * 3600 + int(start_minutes) * 60 + int(start_seconds) + int(start_milliseconds) / 1000.0
        
        # 计算时间差
        time_difference = end_total_seconds - start_total_seconds
        
        return time_difference+2
    
    def TTS(self,file_path):
            # 创建临时文件存储生成文本
            
            # 构造 PowerShell 命令
            command = [
                "powershell", "-Command",
                f'cd F5-TTS; '
                f'.\\f5-tts\\python.exe src\\f5_tts\\infer\\infer_list.py '
                f'--ckpt_file "models\\F5-TTS_Emilia-ZH-EN\\F5TTS_Base\\model_1200000.safetensors" '
                f'--model F5-TTS --load_vocoder_from_local '
                f'--ref_audio "{self.rf_audio}" '
                f'--ref_text "{self.rf_text}" '
                f'--tfp {file_path} --output_dir "..\\temp"'
                f'--vlp "models\\models--charactr--vocos-mel-24khz\\snapshots\\0feb3fdd929bcd6649e0e7c5a688cf7dd012ef21\\pytorch_model.bin"'
            ]
            print(command)
            # 执行命令
            try:
                process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                stdout, stderr = process.communicate()
                print(stdout.decode())
                if stderr:
                    print(stderr.decode(), file=sys.stderr)
            except Exception as e:
                self.info.emit(e)
                self.error_signal.emit("TTS生成失败！")
                self.finished.emit()
                

    def TextToTTS(self,gid):
        headers = {
        "Accept": "application/json, text/plain, */*",
        "Accept-Encoding": "gzip, deflate, br, zstd",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
        "Authorization": self.Authorization,
        "Connection": "keep-alive",
        "Content-Length": "40",
        "Content-Type": "application/x-www-form-urlencoded",
        "Host": "moral.fifedu.com",
        "Origin": "https://static.fifedu.com",
        "Referer": "https://static.fifedu.com/",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-site",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
        "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "Windows",
        "source": "10003"
        }
        data = {
            "levelId":gid
        }
        with open("./cookie.json", 'r') as f:
            cookies = json.load(f)
        session = requests.Session()
        for cookie in cookies:
            session.cookies.set(cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'], secure=cookie['secure'])
        response = session.post('https://moral.fifedu.com/kyxl-app/column/getLevelInfo',headers=headers,data=data)
        content = response.json()
        pre = content['data']['resourcePrefixPath']
        tails = content['data']['content']['moshi']
        x=0
        TTS_temp_list = []
        for tail in tails:
            if tail['name'] == '挑战':
                self.ShowType = tail['showtype'] # 记录挑战类型
                for o in tail['question']['qcontent']['item']:
                    for m in o['questions']:
                        title = re.sub('<.*?>', '', m['title'])
                        TTS_temp_list.append(title)
                        if self.ShowType == "t12":
                            who = m['photo']
                            self.audio_info.append([f'{x}.mp3',re.sub(r'\s+', '',m['title']),re.sub(r'\s+', '',m['title_cn']),1,who])
                            x+=1
                            continue
                        
                        self.audio_info.append([f'{x}.mp3',re.sub(r'\s+', '',m['title']),re.sub(r'\s+', '',m['title_cn']),1])
                        x+=1
        file_path = os.path.join(os.getcwd(), 'temp', 'text.txt')
        with open(file_path,'w',encoding='utf-8') as f:
            for i in TTS_temp_list:
                # 如果i为一个单词
                if len(i.split()) == 1:
                    i = ".\t"+i+".\t"
                f.write(i+'\n')
        self.info.emit("信息获取完成，开始TTS合成音频")
        start_time = time.time()
        self.TTS(file_path)
        for i in range(len(self.audio_info)):
            audio, sr = librosa.load(f'temp/{i}.mp3',sr=None)
            duration = librosa.get_duration(y=audio, sr=sr)
            self.audio_info[i][3] = duration
        self.info.emit("TTS合成完成！用时：{}秒".format(time.time()-start_time))

        return x

    def get_mp3(self,gid):
        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Encoding": "gzip, deflate, br, zstd",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
            "Authorization": self.Authorization,
            "Connection": "keep-alive",
            "Content-Length": "40",
            "Content-Type": "application/x-www-form-urlencoded",
            "Host": "moral.fifedu.com",
            "Origin": "https://static.fifedu.com",
            "Referer": "https://static.fifedu.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
            "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "Windows",
            "source": "10003"
        }
        data = {
            "levelId":gid
        }
        with open("./cookie.json", 'r') as f:
            cookies = json.load(f)
        session = requests.Session()
        for cookie in cookies:
            session.cookies.set(cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'], secure=cookie['secure'])
        response = session.post('https://moral.fifedu.com/kyxl-app/column/getLevelInfo',headers=headers,data=data)
        content = response.json()
        pre = content['data']['resourcePrefixPath']
        tails = content['data']['content']['moshi']
        x=0
        for tail in tails:
            if tail['name'] == '挑战':
                self.ShowType = tail['showtype'] # 记录挑战类型
                for o in tail['question']['qcontent']['item']:
                    for m in o['questions']:
                        url = pre + m['res_path']
                        r =requests.get(url)
                        if self.ShowType == "t12":
                            with open(f'temp/{x}.mp3','wb') as f:
                                f.write(r.content)
                            audio, sr = librosa.load(f'temp/{x}.mp3')
                            duration = librosa.get_duration(y=audio, sr=sr)
                            who = m['photo']
                            self.audio_info.append([f'{x}.mp3',re.sub(r'\s+', '',m['title']),re.sub(r'\s+', '',m['title_cn']),duration,who])
                            x+=1
                            continue
                        with open(f'temp/{x}.mp3','wb') as f:
                            f.write(r.content)
                        audio, sr = librosa.load(f'temp/{x}.mp3')
                        duration = librosa.get_duration(y=audio, sr=sr)
                        self.audio_info.append([f'{x}.mp3',re.sub(r'\s+', '',m['title']),re.sub(r'\s+', '',m['title_cn']),duration])
                        x+=1
        return x
    def time_to_seconds(self,time_str):
        hours, minutes = map(int, time_str.split(':'))
        total_seconds = hours * 60 + minutes - 2
        return total_seconds

    def merge_mp3(self, output_file="t6.mp3"):
        # 设置FFmpeg的路径为当前文件夹下的config文件夹中
        AudioSegment.ffmpeg = os.path.join(os.getcwd(), 'ffmpeg', 'ffmpeg.exe')

        # 音频文件位于temp文件夹下
        mp3_files_dir = 'temp'
        mp3_files = [os.path.join(mp3_files_dir, f) for f in os.listdir(mp3_files_dir) if f.endswith('.mp3')]

        # 使用自然排序，确保文件按正确的顺序排序
        mp3_files = natsort.natsorted(mp3_files)

        # 初始化一个空的音频段
        combined = AudioSegment.empty()
        # 遍历文件列表，加载并合并音频
        for file in mp3_files:
            sound = AudioSegment.from_file(file)
            combined += sound

        # 确保输出目录存在
        output_path = os.path.join('temp', output_file)
        combined.export(output_path, format="mp3")

        # 打印合并后的文件信息
        self.info.emit(f"合并文件在: {output_file}")
        
        # 获取音频信息
        self.audio_info = []
        audio, sr = librosa.load(output_path)
        duration = librosa.get_duration(y=audio, sr=sr)
        self.audio_info.append([output_file, "", "", duration])
    def push_and_play_audio(self,audio_path, t,adb_path, device_path="/sdcard/"):
        # 获取文件名
        filename = os.path.basename(audio_path)

        # 推送文件到设备
        push_command = [adb_path, "push", audio_path, os.path.join(device_path, filename)]
        subprocess.run(push_command,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        # 播放音频文件
        play_command = [adb_path, "shell", "am", "start", "-a", "android.intent.action.VIEW", "-t", "audio/*", 
                        "-d", f"file://{os.path.join(device_path, filename)}"]
        subprocess.run(play_command,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        # 等待音频播放的时长
        time.sleep(math.ceil(t))

        # 删除手机上的文件
        delete_command = [adb_path, "shell", "rm", os.path.join(device_path, filename)]
        subprocess.run(delete_command,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    def adjusted_volume(self):
        adjusted_command = [self.adb_path, "shell", "settings", "put", "system", "volume_music", "8"]
        subprocess.run(adjusted_command,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    def custom_wait(self,driver, selector):
        """自定义等待条件：检查元素是否存在，同时检查 stop_flag"""
        def _predicate(driver):
            if self.stop_flag:
                return True  # 如果 stop_flag 为 True，立即返回
            try:
                element = driver.find_element(By.CSS_SELECTOR, selector)
                return element
            except:
                return False
        return _predicate
    def stop_task(self):
        self.thread_running.emit(True)
        while not self.stop_flag:
            if os.path.exists("sign.txt"):
                os.remove("sign.txt")
                self.stop_flag = True
                self.thread_running.emit(False)
                break
            time.sleep(1)  # 每隔 1 秒检查一次
    def FD(self,driver):
        driver.switch_to.frame(driver.find_element(By.TAG_NAME,'iframe'))
        driver.find_element(By.ID,'tab-tab_active4').click()
        button = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[class="el-button el-button--primary"]'))
        )
        self.stop_flag = False
        
        time.sleep(1)
        button.click()
        time.sleep(4)
        thread_stop = threading.Thread(target=self.stop_task)
        thread_stop.start()
        driver.implicitly_wait(0)
        while not self.stop_flag:
            try:
                WebDriverWait(driver, 60).until(
                    self.custom_wait(driver, 'button[class="taste-button ready-button ing"]')
                )
                if self.stop_flag:
                    break
                time.sleep(random.randint(3,6))
                button = WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
                button.click()
            except:
                driver.quit()
                self.info.emit("error:FD failed")
                self.error_signal.emit("error:FD failed")
                self.finished.emit()
        driver.implicitly_wait(self.implicitly_wait_time)
        driver.switch_to.default_content()
        driver.find_element(By.CSS_SELECTOR,'uni-view[class="bottom_box"]')
        driver.back()
        self.info.emit("提交成功！开始下一题...")
        self.tp+=1
        driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]')
        driver.refresh()
        
    def talk(self,driver):
        WebDriverWait(driver,10).until(EC.element_to_be_clickable(self.parts[self.tp]))
        self.parts[self.tp].click()
        time.sleep(0.5)
        try:
            self.parts[self.tp].click()
        except:
            pass
        if self.FastRead:
            self.FD(driver)
            return
        # cookie = driver.get_cookies()
        # with open('./cookie.json','w') as f:
        #     json.dump(cookie,f)
        
        
        #driver.switch_to.default_content()
        if_url = driver.find_element(By.TAG_NAME,'iframe').get_attribute('src')
        match = re.search(r'gId=([^&]*)', if_url)
        if match:
            gid = match.group(1)
        else:
            self.info.emit('error:gid get failed')
            time.sleep(2)
            exit()
        if not self.IS_TTS:
            self.info.emit("TTS off")
            x =  self.get_mp3(gid) # 下载MP3文件
        else:
            self.info.emit("TTS on")
            x = self.TextToTTS(gid) # TTS生成MP3文件
        if self.ShowType == 't6': # t6模式 短文朗读,需要合并
            try:
                self.merge_mp3()
            except:
                self.info.emit('error:merge mp3 failed')
                self.error_signal.emit('error:merge mp3 failed')
                QTimer.singleShot(1000, self.finished.emit)
        if self.ShowType == 't8': # t8模式 考官提问，观点+原因
            self.merge_mp3(output_file='t8.mp3')
        driver.switch_to.frame(driver.find_element(By.TAG_NAME,'iframe'))
        driver.find_element(By.ID,'tab-tab_active4').click()
        button = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[class="el-button el-button--primary"]'))
        )
        time.sleep(1)
        button.click()
        time.sleep(4)
        # mp3_list = os.listdir('./temp')
        x = len(self.audio_info)
        ct9 = 0 # t9模式回答计次

        if self.ShowType == 't12': # t12模式，小组互动 
            self.info.emit("小组互动模式")
            for _ in range(x):
                # 正回答，m12答，w12跳过
                if self.audio_info[_][-1] == 'w12':
                    continue
                self.info.emit("等待小组成员讲述中...")
                WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
                self.push_and_play_audio(f'temp/{self.audio_info[_][0]}',self.audio_info[_][-2],self.adb_path)
                self.info.emit("我方讲述完成。")
                self.driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
            self.info.emit("正向结束！")
            WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
            self.info.emit("反向开始！")
            for _ in range(x):
                # 反回答，m12跳过，w12答
                if self.audio_info[_][-1] == 'm12':
                    continue
                self.push_and_play_audio(f'temp/{self.audio_info[_][0]}',self.audio_info[_][-2],self.adb_path)
                self.info.emit("我方讲述完成。")
                self.driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
                if _ == x-2 or _ == x-1:
                    break
                self.info.emit("等待小组成员讲述中...")
                WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
        elif self.ShowType == 't9': # t9模式，个人讲述
            self.info.emit("个人讲述模式")
            temp_list = []
            li = driver.find_elements(By.CSS_SELECTOR,'li[class="clearfix answer play"]')
            for _ in li:
                l = _.find_elements(By.CSS_SELECTOR,'div[class="tips clearfix"]')
                temp_list.append(len(l))
            
            for answer_len in temp_list:
                self.info.emit("等待学霸讲述中...")
                WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
                for _ in range(answer_len):
                    self.push_and_play_audio(f'temp/{self.audio_info[ct9][0]}',self.audio_info[ct9][-1],self.adb_path)
                    ct9 += 1
                driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
                self.info.emit("我方复述完毕!")
        elif self.ShowType == 't8': # t8模式 考官提问，观点+原因
            self.info.emit("简短问答模式")
            if self.ShowType == 't8':
                self.info.emit("等待考官提问和学霸回答中...")
            WebDriverWait(driver,60).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
            self.push_and_play_audio(f'temp/{self.audio_info[0][0]}',self.audio_info[0][-1],self.adb_path)
            time.sleep(1)
            driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
        
        elif self.ShowType == 't7': # t7模式 考官提问
            self.info.emit("考官问答模式")
            self.info.emit("等待考官提问中...")
            WebDriverWait(driver,20).until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')))
            self.push_and_play_audio(f'temp/{self.audio_info[-1][0]}',self.audio_info[-1][-1],self.adb_path)
            time.sleep(1)
            driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
        elif self.ShowType == 't6': # t6模式 短文朗读
            self.info.emit("短文朗读模式")
            self.info.emit("短文朗读中...")
            self.push_and_play_audio(f'temp/{self.audio_info[0][0]}',self.audio_info[0][-1],self.adb_path)
            time.sleep(1)
            driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
        
        else: # 口语基础模式
            self.info.emit("基础口语模式")
            talkUI = driver.find_element(By.ID,'talkUl')
            for _ in range(x):
                content = re.sub(r'\s+', '',talkUI.find_elements(By.CSS_SELECTOR,'li')[_].text)
                for i in self.audio_info:
                    if i[1] == content or i[2] == content: # 中文和英文任何一个匹配，则为正确音频
                        t = i[3]
                        audio = i[0]
                        break
                self.push_and_play_audio(f'temp/{audio}',t,self.adb_path)
                driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]').click()
                time.sleep(1)
                if _ == x-1:
                    break
                driver.find_element(By.CSS_SELECTOR,'button[class="taste-button ready-button ing"]')
                time.sleep(1)
                        
        self.audio_info = [] # 清空音频信息

        [os.remove(os.path.join('temp', f)) for f in os.listdir('temp') if os.path.isfile(os.path.join('temp', f))]
        driver.switch_to.default_content()
        driver.find_element(By.CSS_SELECTOR,'uni-view[class="bottom_box"]')
        driver.back()
        self.info.emit("提交成功！开始下一题...")
        self.tp+=1
        driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]')
        driver.refresh()
    def practice(self,driver):
        WebDriverWait(driver,10).until(EC.element_to_be_clickable(self.parts[self.tp]))
        self.parts[self.tp].click()
        time.sleep(0.5)
        try:
            self.parts[self.tp].click()
        except:
            pass
        cookie = driver.get_cookies()
        with open('./cookie_getAnswer.json','w') as f:
            json.dump(cookie,f)
        driver.switch_to.frame(driver.find_element(By.XPATH,'/html/body/iframe'))
        driver.find_element(By.XPATH, "//div[contains(text(), '挑战')]").click()
        driver.switch_to.default_content()
        self.In_to_iframe(driver)
        url = driver.find_element(By.XPATH,'//*[@id="audioPlayersource"]').get_attribute("src")
        gid = url[url.find('level')+6:].split('/')[0]
        self.info.emit(f"正在获取答案中...")
        answers = self._get_answer(gid)
        if not self.T3:
            driver.find_element(By.CSS_SELECTOR,'i[class="van-icon van-icon-play"]').click() # 开始念经...
            time.sleep(5)
            e = driver.find_element(By.CSS_SELECTOR,'div[class="audioBox_text"]').find_element(By.TAG_NAME,'i').text.replace("/","").strip()
            p1 = self.time_to_seconds(e)
            self.info.emit("开始念经...")
            for _ in range(p1):
                percentage = int((_ + 1) / p1 * 100)  # 计算百分比
                self.updateBar.emit(percentage)
                time.sleep(1)
            self.info.emit("念经结束！")
        if answers:
            answer = "修改后的答案："
            for i in answers:
                answer += i
            self.info.emit(answer)
        else:
            self.tp += 1 #获取答案失败，tp++倒回来重新循环一次
            return 
        p = driver.find_element(By.XPATH,'//*[@id="contentBox"]/div/div').find_elements(By.CSS_SELECTOR,'div[class="van-swipe-item"]')
        ti = 0
        for q in p:# 每一页
            ti += 1
            #下面每页的所有题
            for i in q.find_element(By.CSS_SELECTOR, 'div[class="zu_container"]').find_elements(By.CSS_SELECTOR,'div[class="ti_answer"]'):
                i.find_element(By.CSS_SELECTOR,'div[role="radiogroup"]').find_elements(By.CSS_SELECTOR,'div[role="radio"]')[self.PI[answers[0]]].click()
                answers.remove(answers[0])
            if ti == len(p):
                self.info.emit("答题完毕...")
                break
            driver.find_elements(By.CSS_SELECTOR,'em[class="page_right"]')[ti-1].click()
            time.sleep(1.2)
        time.sleep(1.2)
        driver.find_element(By.CSS_SELECTOR, 'i[class="van-icon van-icon-success"]').click()
        time.sleep(0.2)
        try: # 如果没有点击成功再次尝试点击
            driver.find_element(By.CSS_SELECTOR, 'i[class="van-icon van-icon-success"]').click() 
        except:
            pass
        #driver.execute_script("arguments[0].click();", element)
        time.sleep(5) # 等待提交成功
        driver.back()
        self.info.emit("提交成功！开始下一题...")
        self.tp+=1
        answers = []
        driver.find_element(By.CSS_SELECTOR,'uni-view[class="list_content"]').find_elements(By.CSS_SELECTOR,'div[class="list_box"]')
        driver.refresh()
    def is_adb_connected(self):
        try:
            # 执行 adb devices 命令
            result = subprocess.run([self.adb_path, 'devices'], stdout=subprocess.PIPE, shell=True,stderr=subprocess.PIPE, text=True)
            output = result.stdout.strip()
            # 判断输出中是否包含 "device" 关键词，表示有设备连接
            brand_result = subprocess.run([self.adb_path, 'shell', 'getprop', 'ro.product.brand'], 
                                        stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,shell=True)
            if 'device' in output:
                return True,brand_result
            else:
                return False
        except Exception as e:
            # 如果执行命令出现异常，则认为ADB未连接
            self.error_signal.emit(f"ADB连接异常,请查看控制台输出信息")
            self.info.emit(f"执行ADB命令出现异常：{e}")
            return False
    def Init(self,driver):
        if not self.FastRead:
            self.info.emit("正在获取Authorization...")
            for _ in range(2):
                self.Authorization,self.cookies = self._get_Authorization(driver)
                if self.T1: # 如果T1为True，获取Authorization成功，退出循环。
                    break
                self.info.emit(f"重新获取Authorization 第{_+1}次尝试...")
                time.sleep(2)
            if not self.T1: # 如果T1为False，获取Authorization失败，退出程序。
                self.info.emit("获取Authorization失败，退出程序。")
                driver.quit()
                time.sleep(2)
                self.finished.emit()
        else:
            with open("./temp_info/Authorization.txt","r",encoding="utf-8") as f:
                self.Authorization = f.read()
            with open("./temp_info/cookies.json","r",encoding="utf-8") as f:
                self.cookies = json.load(f)
            if self.Authorization != "" and self.cookies != "":
                self.T1 = True
                self.info.emit("加载Authorization and cookies成功！")
                self.resp_proxy = {
                    "http": "http://127.0.0.1:8080",
                    "https": "http://127.0.0.1:8080"
                }
            else:
                self.info.emit("加载Authorization and cookies失败，退出程序。")
                driver.quit()
                time.sleep(2)
                self.finished.emit()
        # cookie = driver.get_cookies()
        # with open('./cookie.json','w') as f:
        #     json.dump(cookie,f)
        current_url = driver.current_url
        unitid_match = re.search(r"unitid=([^&]*)", current_url)
        taskid_match = re.search(r"taskId=([^&]*)", current_url)
        unitid = unitid_match.group(1) if unitid_match else None
        taskid = taskid_match.group(1) if taskid_match else None

        # 设置params
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
            "Accept": "*/*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
            "Authorization": self.Authorization,  # 这里应该是您的授权令牌
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Origin": "https://static.fifedu.com",  # 修正了HTML实体
            "Pragma": "no-cache",
            "Referer": "https://static.fifedu.com",  # 修正了HTML实体
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "content-type": "application/x-www-form-urlencoded",
            "sec-ch-ua-mobile": "?0",
            "source": "10003"
        }
        params = {
            "unitId": unitid,
            'taskId': taskid,
            'bankType': ""
        }
        resp = requests.get('https://moral.fifedu.com/kyxl-app/stu/column/stuUnitInfo', params=params, headers=headers,cookies=self.cookies,proxies=self.resp_proxy,verify=False)
        if resp.status_code == 200:
            resp = resp.json()
            levels = resp['data']['levelList']
            for level in levels:
                i = level['levelContent']['moshi']
                for s in i:
                    if s['name'] =='挑战':
                        i=s
                        break
                i = i['question']['qcontent']['item'][0]['questions'][0]['res_path']
                if str(i) != '[]':
                    self.levelcategory.append(0) # 口语
                else:
                    self.levelcategory.append(1) # 练习
            self.info.emit("获取levelcategory成功！")
    @pyqtSlot()
    def close_Event(self):
        try:
            # 强行终止 WebDriver 进程
            process = psutil.Process(self.driver.service.process.pid)
            for child in process.children(recursive=True):
                child.kill()
            process.kill()
            self.info.emit("driver强行关闭成功！")
        except:
            pass
